1 // Licensed under the Apache License, Version 2.0 (the "License");
2 // you may not use this file except in compliance with the License.
3 // You may obtain a copy of the License at
4 //
5 // http://www.apache.org/licenses/LICENSE-2.0
6 //
7 // Unless required by applicable law or agreed to in writing, software
8 // distributed under the License is distributed on an "AS IS" BASIS,
9 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 // See the License for the specific language governing permissions and
11 // limitations under the License.
12
13 package org.apache.tapestry5.internal.plastic;
14
15 import java.util.Map;
16 import java.util.Set;
17
18 /**
19 * Used to track which methods are implemented by a base class, which is often needed when transforming
20 * a subclass.
21 */
22 public class InheritanceData
23 {
24 private final InheritanceData parent;
25
26 private final String packageName;
27
28 private final Set<String> methodNames = PlasticInternalUtils.newSet();
29 private final Map<String, Boolean> methods = PlasticInternalUtils.newMap();
30 private final Set<String> interfaceNames = PlasticInternalUtils.newSet();
31
32 public InheritanceData(String packageName)
33 {
34 this(null, packageName);
35 }
36
37 private InheritanceData(InheritanceData parent, String packageName)
38 {
39 this.parent = parent;
40 this.packageName = packageName;
41 }
42
43 /**
44 * Is this bundle for a transformed class, or for a base class (typically Object)?
45 *
46 * @return true if this bundle is for transformed class, false otherwise
47 */
48 public boolean isTransformed()
49 {
50 return parent != null;
51 }
52
53 /**
54 * Returns a new MethodBundle that represents the methods of a child class
55 * of this bundle. The returned bundle will always be {@linkplain #isTransformed() transformed}.
56 *
57 * @param packageName
58 * the package that the child class will be created in
59 * @return new method bundle
60 */
61 public InheritanceData createChild(String packageName)
62 {
63 return new InheritanceData(this, packageName);
64 }
65
66 /**
67 * Adds a new instance method. Only non-private methods should be added (that is, methods which might
68 * be overridden in subclasses). This can later be queried to see if any base class implements the method.
69 *
70 * @param name
71 * name of method
72 * @param desc
73 * describes the parameters and return value of the method
74 * @param samePackageOnly
75 * whether the method can only be overridden in classes that are in the same package
76 */
77 public void addMethod(String name, String desc, boolean samePackageOnly)
78 {
79 methods.put(toValue(name, desc), samePackageOnly);
80 methodNames.add(name);
81 }
82
83
84 /**
85 * Returns true if this class or a transformed parent class contains an implementation of,
86 * or abstract placeholder for, the method.
87 *
88 * @param name
89 * method name
90 * @param desc
91 * method descriptor
92 * @return true if this class or a base class implements the method (including abstract methods)
93 */
94 public boolean isImplemented(String name, String desc)
95 {
96 return checkForMethod(toValue(name, desc), this);
97 }
98
99 /**
100 * Returns true if the method is an override of a base class method
101 *
102 * @param name
103 * method name
104 * @param desc
105 * method descriptor
106 * @return true if a base class implements the method (including abstract methods)
107 */
108 public boolean isOverride(String name, String desc)
109 {
110 return checkForMethod(toValue(name, desc), parent);
111 }
112
113 private boolean checkForMethod(String value, InheritanceData cursor)
114 {
115
116 String thisPackageName = packageName;
117
118 while (cursor != null)
119 {
120 if (cursor.methods.containsKey(value))
121 {
122 boolean mustBeInSamePackage = cursor.methods.get(value);
123
124 if (!mustBeInSamePackage)
125 {
126 return true;
127 }
128 boolean isInSamePackage = thisPackageName.equals(cursor.packageName);
129
130 if (isInSamePackage)
131 {
132 return true;
133 }
134 }
135
136 cursor = cursor.parent;
137 }
138
139 return false;
140 }
141
142 /**
143 * Returns true if the class represented by this data, or any parent data, implements
144 * the named interface.
145 */
146 public boolean isInterfaceImplemented(String name)
147 {
148 InheritanceData cursor = this;
149
150 while (cursor != null)
151 {
152 if (cursor.interfaceNames.contains(name))
153 {
154 return true;
155 }
156
157 cursor = cursor.parent;
158 }
159
160 return false;
161 }
162
163 public void addInterface(String name)
164 {
165 if (!interfaceNames.contains(name))
166 {
167 interfaceNames.add(name);
168 }
169 }
170
171 /**
172 * Combines a method name and its desc (which describes parameter types and return value) to form
173 * a value, which is how methods are tracked.
174 */
175 private static String toValue(String name, String desc)
176 {
177 // TAP5-2268: ignore return-type to avoid methods with the same number (and type) of parameters but different
178 // return-types which is illegal in Java.
179 // desc is something like "(I)Ljava/lang/String;", which means: takes an int, returns a String. We strip
180 // everything after the parameter list.
181 int endOfParameterSpecIdx = desc.indexOf(')');
182
183 return name + ":" + desc.substring(0, endOfParameterSpecIdx+1);
184 }
185
186 /**
187 * Returns the names of any methods in this bundle, or from any parent bundles.
188 */
189 public Set<String> methodNames()
190 {
191 Set<String> result = PlasticInternalUtils.newSet();
192
193 InheritanceData cursor = this;
194
195 while (cursor != null)
196 {
197 result.addAll(cursor.methodNames);
198 cursor = cursor.parent;
199 }
200
201 return result;
202 }
203 }